
#ifndef NOSAL_EXEC_INTERNAL_FUNCTION_GROUP_H_
#define NOSAL_EXEC_INTERNAL_FUNCTION_GROUP_H_

#include "startup_configuration.h"
#include "config/state.h"

#include "nosal/core/optional.h"
#include "nosal/log/logger.h"
#include "nosal/exec/execution_error_event.h"

#include <map>
#include <string>
#include <vector>

namespace netaos {
namespace nosal {
namespace exec {
namespace internal {

/// @brief The FunctionGroup class represents a particular Function Group
/// @note This class is for EM-internal purposes only and must not be confused with the specified FunctionGroup class.
class FunctionGroup final
{
public:
    /// @brief Constructor of an EM-internal FunctionGroup instance
    /// @param name name of the FunctionGroup
    /// @param states vector of states of the FunctionGroup
    FunctionGroup(const std::string& name, const std::vector<config::State>& states);

    /// @brief Deleted copy constructor, no copying foreseen.
    FunctionGroup(const FunctionGroup&) = delete;

    /// @brief Deleted copy assignment operator, no copying foreseen.
    FunctionGroup& operator=(const FunctionGroup&) = delete;

    /// @brief Move constructor.
    FunctionGroup(FunctionGroup&&) = default;

    /// @brief Move assignment operator.
    FunctionGroup& operator=(FunctionGroup&&) = default;

    /// @brief Destructor.
    ~FunctionGroup() = default;

    /// @brief Return the name of the FunctionGroup.
    /// @returns Name of the FunctionGroup.
    const std::string& GetName() const;

    /// @brief Return the name of the current state of the FunctionGroup.
    /// @returns Name of the current state.
    const std::string& GetCurrentStateName() const;

    /// @brief Return a list of all states of the FunctionGroup.
    /// @returns Vector with all states.
    const std::vector<config::State>& GetAllStates() const;

    /// @brief Set the current state of the FunctionGroup.
    /// @param[in] state_name Name of the state.
    void SetCurrentState(const std::string& state_name);

    /// @brief Update the state by checking for failed processes.
    void UpdateState();

    /// @brief Check whether a state transition is finished by checking the ProcessStates of all affected processes.
    /// @returns true if transition finished, false otherwise.
    bool IsTransitionFinished() const;

    /// @brief Check whether FunctionGroup is in Undefined FunctionGroupState.
    /// @returns true if FunctionGroup is in Undefined state, false otherwise.
    bool IsCurrentStateFailed() const;

    /// @brief Check whether FunctionGroup has a specific state identified by its name.
    /// @param[in] state_name Name of the FunctionGroupState.
    /// @returns true if FunctionGroup has such a state, false otherwise.
    bool HasState(const std::string& state_name) const;

    /// @brief Get a list of StartupConfigurations that are part of a specified state of the FunctionGroup.
    /// @param[in] state_name Name of the FunctionGroupState.
    /// @returns Vector with pointers to the StartupConfigurations assigned to this state.
    std::vector<std::shared_ptr<StartupConfiguration>> GetStartupConfigurations(
        const std::string& state_name) const;

    /// @brief Add a StartupConfiguration reference to a specified state.
    /// @param[in] state_name Name of the state to which the StartupConfiguration shall be assigned to.
    /// @param[in, out] startup_configuration Shared pointer to the StartupConfiguration created by an external source.
    void AddStartupConfig(const std::string& state_name,
        std::shared_ptr<StartupConfiguration> startup_configuration);

    /// @brief Check whether a StartupConfiguration is required to run in current state of the FunctionGroup.
    /// @param[in] startup_configuration StartupConfiguration to check for.
    /// @returns true if StartupConfiguration is required to run in current state, false otherwise.
    bool IsStartupConfigurationRequiredNow(std::shared_ptr<StartupConfiguration> const startup_configuration) const;

    /// @brief Get the ExecutionError via its Optional.
    /// @returns Optional of the ExecutionError.
    nosal::core::Optional<nosal::exec::ExecutionError> GetExecutionError() const;

    /// @brief Set the FunctionGroupState to UndefinedState after a failure occurred in a StartupConfiguration.
    /// @param[in] error ExecutionError of the faulty StartupConfiguration.
    void SetUndefinedFunctionGroupState(const nosal::exec::ExecutionError& error);

    /// @brief Remove a StartupConfiguration from the FunctionGroup.
    /// @param startup_configuration_name Name of the StartupConfiguration instance.
    void RemoveStartupConfigFromGroup(const std::string& startup_configuration_name);

    /// @brief Checks whether a specific StartupConfiguration is configured to run in some state of the FG.
    /// @param[in] startup_configuration_name Name of the StartupConfiguration to be checked for.
    /// @returns true if StartupConfiguration is configured to run in some state of the FG, else false.
    bool IsStartupConfigurationPartOfGroup(const std::string& startup_configuration_name);

    /// @brief Checks if the FunctionGroup was set to UndefinedState during a transition.
    /// @return true the first time this function is called after the FunctionGroup was set to UndefinedState.
    bool HasUnhandledStateFailedEvent();

private:
    /// @brief Find a state by its name.
    /// @param[in] state_name Name of the state.
    /// @returns Pointer to the state.
    const config::State* FindState(const std::string& state_name) const;

    /// @brief Get the current state of the FunctionGroup.
    /// @returns Reference to the current State.
    const config::State& GetCurrentState() const;

    /// @brief Finds a process that terminated abnormally
    /// @details This method returns a first failed process found and ignores all other failed processes if any exist.
    /// @returns first found failed process or empty object if there is no any failed process.
    netaos::nosal::core::Optional<const std::shared_ptr<StartupConfiguration>> FindFailedProcess();

    /// @brief Checks whether the current state of the FunctionGroup is either Off or Undefined.
    /// @returns true if current state is either Off or Undefined, false otherwise.
    bool IsCurrentStateOffOrUndefined() const;

    /// @brief Off state.
    static const config::State kOffState;

    /// @brief Undefined state.
    static const config::State kUndefinedState;

    /// @brief Name of the FunctionGroup.
    const std::string name_;

    /// @brief List of all states of this FunctionGroup.
    const std::vector<config::State>& all_states_;

    /// @brief Pointer to the current_state of the FunctionGroup.
    const config::State* current_state_;

    /// @brief Map of all state names with the StartupConfigurations assigned to the respective state.
    std::map<std::string, std::set<std::shared_ptr<StartupConfiguration>>> startup_configurations_;

    /// @brief Optional containing the ExecutionError in case a process is failed.
    netaos::nosal::core::Optional<netaos::nosal::exec::ExecutionError> execution_error_;

    /// @brief Check whether empty FunctionGroupState warning was issued before
    /// @note This is to prevent repeated emission of the same log message.
    bool was_warning_issued_before_{false};

    /// @brief A flag indicating that the FunctionGroup changed to Undefined State during a failed state transition.
    bool unhandled_state_fail_{false};
};

}  // namespace internal
}  // namespace exec
}  // namespace nosal
}  // namespace netaos

#endif  // NOSAL_EXEC_INTERNAL_FUNCTION_GROUP_H_
